/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_light.cpp - Setups the light for rendering and handles editor events
//

// TODO:
// - Shadows still takes way too much FPS with 60+ lights...
// - Light PVS culling
// - Pre-caching of interaction lists and structure parameters
// - Add inline models interaction when R_AddInlineModel culling works as it should
// - MD2 models are culling lights wrong or something because shadows/lights keeping going out and in


#include "r_local.h"


#define MAX_STATIC_LIGHTS				4096

#define MAX_LIGHT_PLANE_VERTICES		64

#define LIGHT_PLANEBITS					(BIT(6) - 1)

struct nearClipVolume_t {
	bool					FIXME;
	bool					degenerate;
	cplane_t				frustum[5];
};

static nearClipVolume_t		r_nearClipVolume;

static mesh_t				r_shadowsList[2][MAX_MESHES];
static mesh_t				r_interactionsList[2][MAX_MESHES];

static lightCache_t			*r_cachedLights[MAX_STATIC_LIGHTS];
static int					r_numCachedLights;

static lightSource_t		r_staticLights[MAX_STATIC_LIGHTS];
static int					r_numStaticLights;


/*
 ==============================================================================

    PRE-CACHE STATIC LIGHTS

 ==============================================================================
*/


// TODO: Get the lightSource data and cache it into lightCache_t. Then compare the current light been renderer against lightCache_t and see
// if any of the parameters were changed, if not, copy them into light_t.

static void R_PrecacheLights ()
{
}


/*
 ==============================================================================

    LIGHT PARSING

 ==============================================================================
*/


/*
 ==================
 R_ParseLights
 ==================
*/
static void R_ParseLights (script_t *script)
{
	token_t			token;
	lightSource_t   *lightSource;
	vec3_t			origin, center, angles, radius;
	int				style, detailLevel;
	bool			parallel, noShadows;
	material_t		*material;
	float			materialParms[MAX_MATERIAL_PARMS];
	vec3_t			tmp;
	float			dot;
	int				i;

	while (1){
		if (!PS_ReadToken(script, &token))
			break;		// End of data

		if (!Str_ICompare(token.string, "{"))
		{
			// Set default values
			VectorClear(origin);
			VectorClear(center);
			VectorClear(angles);
			VectorSet(radius, 128, 128, 128);

			style = 0;
			detailLevel = 10;
			parallel = false;
			noShadows = false;

			material = rg.defaultLightMaterial;
			materialParms[MATERIALPARM_RED] = 1.0f;
			materialParms[MATERIALPARM_GREEN] = 1.0f;
			materialParms[MATERIALPARM_BLUE] = 1.0f;
			materialParms[MATERIALPARM_ALPHA] = 1.0f;
			materialParms[MATERIALPARM_TIME_OFFSET] = 0.0f;
			materialParms[MATERIALPARM_DIVERSITY] = 0.0f;
			materialParms[MATERIALPARM_GENERAL] = 0.0f;
			materialParms[MATERIALPARM_MODE] = 0.0f;

			// Parse the light
			while (1){
				if (!PS_ReadToken(script, &token))
				{
					Com_Printf(S_COLOR_YELLOW "WARNING: no concluding '}' in lights file\n");
					return;   // End of data
				}

				if (!Str_ICompare(token.string, "}"))
					break;   // End of light

				// Parse the field
				if (!Str_ICompare(token.string, "origin"))
				{
					for (i = 0; i < 3; i++)
					{
						if (!PS_ReadFloat(script, &origin[i]))
						{
							Com_Printf(S_COLOR_YELLOW "WARNING: missing 'origin' parameters for light\n");
							return;
						}
					}
				}
				else if (!Str_ICompare(token.string, "center"))
				{
					for (i = 0; i < 3; i++)
					{
						if (!PS_ReadFloat(script, &center[i]))
						{
							Com_Printf(S_COLOR_YELLOW "WARNING: missing 'center' parameters for light\n");
							return;
						}
					}
				}
				else if (!Str_ICompare(token.string, "angles"))
				{
					for (i = 0; i < 3; i++)
					{
						if (!PS_ReadFloat(script, &angles[i]))
						{
							Com_Printf(S_COLOR_YELLOW "WARNING: missing 'angles' parameters for light\n");
							return;
						}
					}
				}
				else if (!Str_ICompare(token.string, "radius"))
				{
					for (i = 0; i < 3; i++)
					{
						if (!PS_ReadFloat(script, &radius[i]))
						{
							Com_Printf(S_COLOR_YELLOW "WARNING: missing 'radius' parameters for light\n");
							return;
						}

						if (radius[i] <= 0.0f)
						{
							Com_Printf(S_COLOR_YELLOW "WARNING: invalid 'radius' value of %f for light\n", radius[i]);
							return;
						}
					}
				}
				else if (!Str_ICompare(token.string, "color"))
				{
					for (i = 0; i < 3; i++)
					{
						if (!PS_ReadFloat(script, &materialParms[i]))
						{
							Com_Printf(S_COLOR_YELLOW "WARNING: missing 'color' parameters for light\n");
							return;
						}

						if (materialParms[i] < 0.0f)
						{
							Com_Printf(S_COLOR_YELLOW "WARNING: invalid 'color' value of %f for light\n", materialParms[i]);
							return;
						}
					}
				}
				else if (!Str_ICompare(token.string, "style"))
				{
					if (!PS_ReadInteger(script, &style))
					{
						Com_Printf(S_COLOR_YELLOW "WARNING: missing 'style' parameters for light\n");
						return;
					}

					if (style < 0 || style >= MAX_LIGHTSTYLES)
					{
						Com_Printf(S_COLOR_YELLOW "WARNING: invalid 'style' value of %i for light\n", style);
						return;
					}
				}
				else if (!Str_ICompare(token.string, "detailLevel"))
				{
					if (!PS_ReadInteger(script, &detailLevel))
					{
						Com_Printf(S_COLOR_YELLOW "WARNING: missing 'detailLevel' parameters for light\n");
						return;
					}

					if (detailLevel < 0 || detailLevel > 10)
					{
						Com_Printf(S_COLOR_YELLOW "WARNING: invalid 'detailLevel' value of %i for light\n", detailLevel);
						return;
					}
				}
				else if (!Str_ICompare(token.string, "parallel"))
					parallel = true;
				else if (!Str_ICompare(token.string, "noShadows"))
					noShadows = true;
				else if (!Str_ICompare(token.string, "material"))
				{
					if (!PS_ReadToken(script, &token))
					{
						Com_Printf(S_COLOR_YELLOW "WARNING: missing 'material' parameters for light\n");
						return;
					}

					material = R_FindMaterial(token.string, MT_LIGHT, 0);
				}
				else if (!Str_ICompare(token.string, "materialParm3"))
				{
					if (!PS_ReadFloat(script, &materialParms[3]))
					{
						Com_Printf(S_COLOR_YELLOW "WARNING: missing 'materialParm3' parameters for light\n");
						return;
					}
				}
				else if (!Str_ICompare(token.string, "materialParm4"))
				{
					if (!PS_ReadFloat(script, &materialParms[4]))
					{
						Com_Printf(S_COLOR_YELLOW "WARNING: missing 'materialParm4' parameters for light\n");
						return;
					}
				}
				else if (!Str_ICompare(token.string, "materialParm5"))
				{
					if (!PS_ReadFloat(script, &materialParms[5]))
					{
						Com_Printf(S_COLOR_YELLOW "WARNING: missing 'materialParm5' parameters for light\n");
						return;
					}
				}
				else if (!Str_ICompare(token.string, "materialParm6"))
				{
					if (!PS_ReadFloat(script, &materialParms[6]))
					{
						Com_Printf(S_COLOR_YELLOW "WARNING: missing 'materialParm6' parameters for light\n");
						return;
					}
				}
				else if (!Str_ICompare(token.string, "materialParm7"))
				{
					if (!PS_ReadFloat(script, &materialParms[7]))
					{
						Com_Printf(S_COLOR_YELLOW "WARNING: missing 'materialParm7' parameters for light\n");
						return;
					}
				}
				else
				{
					Com_Printf(S_COLOR_YELLOW "WARNING: unknown field '%s' for light\n", token.string);
					return;
				}
			}

			// Add it to the list
			if (r_numStaticLights == MAX_STATIC_LIGHTS)
				Com_Error(ERR_DROP, "R_ParseLights: MAX_STATIC_LIGHTS hit");

			lightSource = &r_staticLights[r_numStaticLights++];

			lightSource->index = r_numStaticLights - 1;
			VectorCopy(origin, lightSource->origin);
			VectorCopy(center, lightSource->center);
			VectorCopy(angles, lightSource->angles);
			VectorCopy(radius, lightSource->radius);
			lightSource->style = style;
			lightSource->detailLevel = detailLevel;
			lightSource->parallel = parallel;
			lightSource->noShadows = noShadows;
			lightSource->material = material;
			lightSource->materialParms[0] = materialParms[0];
			lightSource->materialParms[1] = materialParms[1];
			lightSource->materialParms[2] = materialParms[2];
			lightSource->materialParms[3] = materialParms[3];
			lightSource->materialParms[4] = materialParms[4];
			lightSource->materialParms[5] = materialParms[5];
			lightSource->materialParms[6] = materialParms[6];
			lightSource->materialParms[7] = materialParms[7];

			// Compute axes
			AnglesToAxis(angles, lightSource->axis);

			// Compute the corners of the bounding volume
			for (i = 0; i < 8; i++)
			{
				tmp[0] = (i & 1) ? -lightSource->radius[0] : lightSource->radius[0];
				tmp[1] = (i & 2) ? -lightSource->radius[1] : lightSource->radius[1];
				tmp[2] = (i & 4) ? -lightSource->radius[2] : lightSource->radius[2];

				// Rotate and translate
				VectorRotate(tmp, lightSource->axis, lightSource->corners[i]);
				VectorAdd(lightSource->corners[i], lightSource->origin, lightSource->corners[i]);
			}

			// Check if it's rotated
			lightSource->rotated = !VectorCompare(angles, vec3_origin);

			// Compute mins/maxs
			VectorSubtract(lightSource->origin, lightSource->radius, lightSource->mins);
			VectorAdd(lightSource->origin, lightSource->radius, lightSource->maxs);

			// Compute frustum planes
			for (i = 0; i < 3; i++)
			{
				dot = DotProduct(lightSource->origin, lightSource->axis[i]);

				VectorCopy(lightSource->axis[i], lightSource->frustum[i*2+0].normal);
				lightSource->frustum[i*2+0].dist = dot - lightSource->radius[i];
				lightSource->frustum[i*2+0].type = PlaneTypeForNormal(lightSource->frustum[i*2+0].normal);
				SetPlaneSignbits(&lightSource->frustum[i*2+0]);

				VectorNegate(lightSource->axis[i], lightSource->frustum[i*2+1].normal);
				lightSource->frustum[i*2+1].dist = -dot - lightSource->radius[i];
				lightSource->frustum[i*2+1].type = PlaneTypeForNormal(lightSource->frustum[i*2+1].normal);
				SetPlaneSignbits(&lightSource->frustum[i*2+1]);
			}

			// Find the leaf
			VectorAdd(lightSource->origin, lightSource->center, tmp);

			lightSource->leaf = R_PointInLeaf(tmp);
		}
		else
		{
			Com_Printf(S_COLOR_YELLOW "WARNING: expected '{', found '%s' instead in lights file\n", token.string);
			return;
		}
	}
}

/*
 ==================
 R_LoadLights
 ==================
*/
void R_LoadLights ()
{
	script_t	*script;
	char		name[MAX_PATH_LENGTH];

	r_numStaticLights = 0;

	// Load the script file
	Str_SPrintf(name, sizeof(name), "%s.txt", rg.worldModel->name);

	script = PS_LoadScriptFile(name);
	if (!script)
		return;

	PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_ALLOWPATHNAMES);

	// Parse it
	R_ParseLights(script);

	PS_FreeScript(script);

	// Pre-cache static lights
	R_PrecacheLights();
}


/*
 ==============================================================================

    UTILITIES

 ==============================================================================
*/


/*
 ==================
 R_BoxInNearClipVolume

 Returns true if the box is inside the clip volume
 ==================
*/
static bool R_BoxInNearClipVolume (const vec3_t mins, const vec3_t maxs)
{
	int i;

	if (r_nearClipVolume.FIXME)
		return true;

	if (r_nearClipVolume.degenerate)
	{
		if (BoxOnPlaneSide(mins, maxs, &rg.renderViewParms.frustum[FRUSTUM_LEFT]) == PLANESIDE_CROSS)
			return true;

		return false;
	}

	for (i = 0; i < NUM_FRUSTUM_PLANES; i++)
	{
		if (BoxOnPlaneSide(mins, maxs, &r_nearClipVolume.frustum[i]) == PLANESIDE_BACK)
			return false;
	}

	return true;
}

/*
 ==================
 R_SphereInNearClipVolume

 Returns true if the sphere is inside the clip volume
 ==================
*/
static bool R_SphereInNearClipVolume (const vec3_t center, float radius)
{
	int		i;

	if (r_nearClipVolume.FIXME)
		return true;

	if (r_nearClipVolume.degenerate)
	{
		if (SphereOnPlaneSide(center, radius, &rg.renderViewParms.frustum[0]) == PLANESIDE_CROSS)
			return true;

		return false;
	}

	for (i = 0; i < NUM_FRUSTUM_PLANES; i++)
	{
		if (SphereOnPlaneSide(center, radius, &r_nearClipVolume.frustum[i]) == PLANESIDE_BACK)
			return false;
	}

	return true;
}

/*
 ==================
 R_SetupNearClipVolume
 ==================
*/
static void R_SetupNearClipVolume (const vec3_t lightOrigin)
{
	mat4_t	invMatrix;
	vec3_t	eyeCorners[4], worldCorners[4];
	float	x, y;
	int		i, side;

	// See which side of the near plane the light is
	side = PointOnPlaneSide(lightOrigin, ON_EPSILON, &rg.renderViewParms.frustum[FRUSTUM_LEFT]);

	if (side == PLANESIDE_ON)
	{
		r_nearClipVolume.degenerate = true;
		return;
	}

	r_nearClipVolume.degenerate = false;

	// Compute the eye space corners of the viewport
	x = r_zNear->floatValue * tan(rg.renderViewParms.fovX * M_PI / 360.0f);
	y = r_zNear->floatValue * tan(rg.renderViewParms.fovY * M_PI / 360.0f);

	VectorSet(eyeCorners[0], x, y, -r_zNear->floatValue);
	VectorSet(eyeCorners[1], -x, y, -r_zNear->floatValue);
	VectorSet(eyeCorners[2], -x, -y, -r_zNear->floatValue);
	VectorSet(eyeCorners[3], x, -y, -r_zNear->floatValue);

	// Transform the corners to world space
	Matrix4_AffineInverse(rg.renderViewParms.worldMatrix, invMatrix);

	Matrix4_TransformVector(invMatrix, eyeCorners[0], worldCorners[0]);
	Matrix4_TransformVector(invMatrix, eyeCorners[1], worldCorners[1]);
	Matrix4_TransformVector(invMatrix, eyeCorners[2], worldCorners[2]);
	Matrix4_TransformVector(invMatrix, eyeCorners[3], worldCorners[3]);

	// Set up the frustum planes
	if (side == PLANESIDE_FRONT)
	{
		VectorCopy(rg.renderViewParms.frustum[FRUSTUM_LEFT].normal, r_nearClipVolume.frustum[FRUSTUM_LEFT].normal);
		r_nearClipVolume.frustum[FRUSTUM_LEFT].dist = rg.renderViewParms.frustum[FRUSTUM_LEFT].dist;

		PlaneFromPoints(&r_nearClipVolume.frustum[FRUSTUM_RIGHT], lightOrigin, worldCorners[1], worldCorners[0]);
		PlaneFromPoints(&r_nearClipVolume.frustum[FRUSTUM_TOP], lightOrigin, worldCorners[2], worldCorners[1]);
		PlaneFromPoints(&r_nearClipVolume.frustum[FRUSTUM_BOTTOM], lightOrigin, worldCorners[3], worldCorners[2]);
		PlaneFromPoints(&r_nearClipVolume.frustum[FRUSTUM_NEAR], lightOrigin, worldCorners[0], worldCorners[3]);
	}
	else
	{
		VectorNegate(rg.renderViewParms.frustum[FRUSTUM_LEFT].normal, r_nearClipVolume.frustum[FRUSTUM_LEFT].normal);
		r_nearClipVolume.frustum[FRUSTUM_LEFT].dist = -rg.renderViewParms.frustum[FRUSTUM_LEFT].dist;

		PlaneFromPoints(&r_nearClipVolume.frustum[FRUSTUM_RIGHT], lightOrigin, worldCorners[0], worldCorners[1]);
		PlaneFromPoints(&r_nearClipVolume.frustum[FRUSTUM_TOP], lightOrigin, worldCorners[1], worldCorners[2]);
		PlaneFromPoints(&r_nearClipVolume.frustum[FRUSTUM_BOTTOM], lightOrigin, worldCorners[2], worldCorners[3]);
		PlaneFromPoints(&r_nearClipVolume.frustum[FRUSTUM_NEAR], lightOrigin, worldCorners[3], worldCorners[0]);
	}

	for (i = 0; i < NUM_FRUSTUM_PLANES; i++)
	{
		r_nearClipVolume.frustum[i].type = PLANE_NON_AXIAL;
		SetPlaneSignbits(&r_nearClipVolume.frustum[i]);
	}
}

/*
 ==================
 R_IsViewInLightVolume

 Returns true if the view is inside the light volume
 ==================
*/
static bool R_IsViewInLightVolume (lightSource_t *lightSource)
{
	cplane_t   *plane;
	int		   i;

	// Check if the view is inside a light volume
	if (!BoundsContainsPoint(lightSource->mins, lightSource->maxs, rg.renderView.viewOrigin))
		return false;

	// Check against frustum planes
	for (i = 0, plane = lightSource->frustum; i < NUM_LIGHT_FRUSTUM_PLANES; i++, plane++)
	{
		if (PointOnPlaneSide(rg.renderView.viewOrigin, 0.0f, plane) == PLANESIDE_BACK)
			return false;
	}

	return true;
}

/*
 ==================
 R_ClipLightPlane
 ==================
*/
static void R_ClipLightPlane (int stage, int numVertices, vec3_t vertices, const mat4_t mvpMatrix, vec2_t mins, vec2_t maxs)
{
	int			i;
	float		*v;
	bool		frontSide;
	vec3_t		front[MAX_LIGHT_PLANE_VERTICES];
	int			f;
	float		dist;
	float		dists[MAX_LIGHT_PLANE_VERTICES];
	int			sides[MAX_LIGHT_PLANE_VERTICES];
	cplane_t	*plane;
	vec4_t		in, out;
	float		x, y;

	if (numVertices > MAX_LIGHT_PLANE_VERTICES-2)
		Com_Error(ERR_DROP, "R_ClipLightPlane: MAX_LIGHT_PLANE_VERTICES hit");

	if (stage == NUM_FRUSTUM_PLANES)
	{
		// Fully clipped, so add screen space points
		for (i = 0, v = vertices; i < numVertices; i++, v += 3)
		{
			in[0] = v[0];
			in[1] = v[1];
			in[2] = v[2];
			in[3] = 1.0f;

			Matrix4_Transform(mvpMatrix, in, out);

			if (out[3] == 0.0f)
				continue;

			out[0] /= out[3];
			out[1] /= out[3];

			x = rg.renderViewParms.viewportX + (0.5f + 0.5f * out[0]) * rg.renderViewParms.viewportWidth;
			y = rg.renderViewParms.viewportY + (0.5f + 0.5f * out[1]) * rg.renderViewParms.viewportHeight;

			mins[0] = min(mins[0], x);
			mins[1] = min(mins[1], y);
			maxs[0] = max(maxs[0], x);
			maxs[1] = max(maxs[1], y);
		}

		return;
	}

	frontSide = false;

	plane = &rg.renderViewParms.frustum[stage];
	for (i = 0, v = vertices; i < numVertices; i++, v += 3)
	{
		if (plane->type < PLANE_NON_AXIAL)
			dists[i] = dist = v[plane->type] - plane->dist;
		else
			dists[i] = dist = DotProduct(v, plane->normal) - plane->dist;

		if (dist > ON_EPSILON)
		{
			frontSide = true;
			sides[i] = PLANESIDE_FRONT;
		}
		else if (dist < -ON_EPSILON)
			sides[i] = PLANESIDE_BACK;
		else
			sides[i] = PLANESIDE_ON;
	}

	if (!frontSide)
		return;   // Not clipped

	// Clip it
	dists[i] = dists[0];
	sides[i] = sides[0];
	VectorCopy(vertices, (vertices + (i*3)));

	f = 0;

	for (i = 0, v = vertices; i < numVertices; i++, v += 3)
	{
		switch (sides[i]){
		case PLANESIDE_FRONT:
			VectorCopy(v, front[f]);
			f++;

			break;
		case PLANESIDE_BACK:

			break;
		case PLANESIDE_ON:
			VectorCopy(v, front[f]);
			f++;

			break;
		}

		if (sides[i] == PLANESIDE_ON || sides[i+1] == PLANESIDE_ON || sides[i+1] == sides[i])
			continue;

		dist = dists[i] / (dists[i] - dists[i+1]);

		front[f][0] = v[0] + (v[3] - v[0]) * dist;
		front[f][1] = v[1] + (v[4] - v[1]) * dist;
		front[f][2] = v[2] + (v[5] - v[2]) * dist;

		f++;
	}

	// Continue
	R_ClipLightPlane(stage+1, f, front[0], mvpMatrix, mins, maxs);
}

/*
 ==================
 R_SetScissorRect
 ==================
*/
static void R_SetScissorRect (light_t *light, lightSource_t *lightSource)
{
	int			cornerIndices[6][4] = {{3, 2, 6, 7}, {0, 1, 5, 4}, {2, 3, 1, 0}, {4, 5, 7, 6}, {1, 3, 7, 5}, {2, 0, 4, 6}};
	mat4_t		mvpMatrix;
	vec3_t		vertices[5];
	vec2_t		mins = {999999, 999999}, maxs = {-999999, -999999};
	int			xMin, yMin, xMax, yMax;
	int			i;

	if (r_skipLightScissors->integerValue || R_IsViewInLightVolume(lightSource))
	{
		light->scissorX = rg.renderViewParms.viewportX;
		light->scissorY = rg.renderViewParms.viewportY;
		light->scissorWidth = rg.renderViewParms.viewportWidth;
		light->scissorHeight = rg.renderViewParms.viewportHeight;

		return;
	}

	// Compute modelview-projection matrix
	Matrix4_Multiply(rg.renderViewParms.perspectiveMatrix, rg.renderViewParms.worldMatrix, mvpMatrix);

	// Copy the corner points of each plane and clip to the frustum
	for (i = 0; i < 6; i++)
	{
		VectorCopy(light->corners[cornerIndices[i][0]], vertices[0]);
		VectorCopy(light->corners[cornerIndices[i][1]], vertices[1]);
		VectorCopy(light->corners[cornerIndices[i][2]], vertices[2]);
		VectorCopy(light->corners[cornerIndices[i][3]], vertices[3]);

		R_ClipLightPlane(0, 4, vertices[0], mvpMatrix, mins, maxs);
	}

	// Set the scissor rectangle
	xMin = max(floor(mins[0]), rg.renderViewParms.viewportX);
	yMin = max(floor(mins[1]), rg.renderViewParms.viewportY);
	xMax = min(ceil(maxs[0]), rg.renderViewParms.viewportX + rg.renderViewParms.viewportWidth);
	yMax = min(ceil(maxs[1]), rg.renderViewParms.viewportY + rg.renderViewParms.viewportHeight);

	if (xMax <= xMin || yMax <= yMin)
	{
		light->scissorX = rg.renderViewParms.viewportX;
		light->scissorY = rg.renderViewParms.viewportY;
		light->scissorWidth = rg.renderViewParms.viewportWidth;
		light->scissorHeight = rg.renderViewParms.viewportHeight;

		return;
	}

	light->scissorX = xMin;
	light->scissorY = yMin;
	light->scissorWidth = xMax - xMin;
	light->scissorHeight = yMax - yMin;
}

/*
 ==================
 R_SetDepthBounds

 TODO!!!
 ==================
*/
static void R_SetDepthBounds (light_t *light, lightSource_t *lightSource)
{
	mat4_t	mvpMatrix;
	vec4_t	in, out;
	float	depth;
	float	depthMin = 999999, depthMax = -999999;
	int		i;

	if (r_skipDepthBounds->integerValue || !glConfig.depthBoundsTest)
	{
		light->depthMin = 0.0f;
		light->depthMax = 1.0f;

		return;
	}

	// Compute modelview-projection matrix
	Matrix4_Multiply(rg.renderViewParms.perspectiveMatrix, rg.renderViewParms.worldMatrix, mvpMatrix);

	// Add screen space points
	for (i = 0; i < 8; i++)
	{
		in[0] = light->corners[i][0];
		in[1] = light->corners[i][1];
		in[2] = light->corners[i][2];
		in[3] = 1.0f;

		Matrix4_Transform(mvpMatrix, in, out);

		if (out[3] == 0.0f)
			continue;

		depth = 0.5f + 0.5f * out[2];

		depthMin = min(depthMin, depth);
		depthMax = max(depthMax, depth);
	}

	// Set the depth bounds
	depthMin = max(depthMin, 0.0f);
	depthMax = min(depthMax, 1.0f);

	if (depthMax <= depthMin)
	{
		light->depthMin = 0.0f;
		light->depthMax = 1.0f;

		return;
	}

	light->depthMin = depthMin;
	light->depthMax = depthMax;
}


/*
 ==============================================================================

    INTERACTION LISTS

 ==============================================================================
*/


/*
 ==================
 R_AddInteractionMeshToLight
 ==================
*/
static void R_AddInteractionMeshToLight (light_t *light, meshType_t type, meshData_t *data, material_t *material, renderEntity_t *entity)
{
	mesh_t	*mesh;

	rg.pc.interactions++;
	rg.pc.interactionsMemory += sizeof(mesh_t);

	if (light->material->fogLight || light->material->blendLight)
		rg.pc.interactionsFog++;
	else
		rg.pc.interactionsLight++;

	if (!material->noSelfShadow || (light->material->fogLight || light->material->blendLight))
	{
		if (light->numInteractions[0] == MAX_MESHES)
			Com_Error(ERR_DROP, "R_AddInteractionMeshToLight: MAX_MESHES hit");

		mesh = &r_interactionsList[0][light->numInteractions[0]++];
	}
	else
	{
		if (light->numInteractions[1] == MAX_MESHES)
			Com_Error(ERR_DROP, "R_AddInteractionMeshToLight: MAX_MESHES hit");

		mesh = &r_interactionsList[1][light->numInteractions[1]++];
	}

	mesh->sort = (material->index << 20) | (entity->index << 9) | (type << 5) | (0);
	mesh->data = data;
}

/*
 ==================
 R_AddShadowMeshToLight
 ==================
*/
static void R_AddShadowMeshToLight (light_t *light, meshType_t type, meshData_t *data, material_t *material, renderEntity_t *entity, bool caps)
{
	mesh_t	*mesh;

	if (!r_shadows->integerValue)
		return;

	rg.pc.shadows++;
	rg.pc.shadowsMemory += sizeof(mesh_t);

	if (caps)
		rg.pc.shadowsZFail++;
	else
		rg.pc.shadowsZPass++;

	if (!material->noSelfShadow)
	{
		if (light->numShadows[0] == MAX_MESHES)
			Com_Error(ERR_DROP, "R_AddShadowMeshToLight: MAX_MESHES hit");

		mesh = &r_shadowsList[0][light->numShadows[0]++];
	}
	else
	{
		if (light->numShadows[1] == MAX_MESHES)
			Com_Error(ERR_DROP, "R_AddShadowMeshToLight: MAX_MESHES hit");

		mesh = &r_shadowsList[1][light->numShadows[1]++];
	}

	mesh->sort = (material->index << 20) | (entity->index << 9) | (type << 5) | ((int)caps);
	mesh->data = data;
}

/*
 ==================
 R_RecursiveInteractionNode

 FIXME: Light PVS culling
 ==================
*/
static void R_RecursiveInteractionNode (light_t *light, lightSource_t *lightSource, const vec3_t lightOrigin, node_t *node, int clipFlags)
{
	leaf_t		*leaf;
	surface_t	*surface, **mark;
	bool		caps;
	int			i;

	if (node->contents == CONTENTS_SOLID)
		return;   // Solid

	if (node->viewCount != rg.viewCount)
	{
		if (light->lightInFrustum || !light->castShadows)
			return;   // Not visible, but may still cast visible shadows
	}

	// Frustum cull
	if (clipFlags)
	{
		if (clipFlags != CULL_IN)
		{
			clipFlags = R_LightCullBox(lightSource, node->mins, node->maxs, clipFlags);

			if (clipFlags == CULL_OUT)
				return;
		}
#if 0
		for (i = 0, plane = lightSource->frustum; i < 6; i++, plane++){
			if (!(clipFlags & (1<<i)))
				continue;

			side = BoxOnPlaneSide(node->mins, node->maxs, plane);

			if (side == SIDE_BACK)
				return;

			if (side == SIDE_FRONT)
				clipFlags &= ~(1<<i);
		}
#endif
	}

	// Recurse down the children
	if (node->contents == -1)
	{
		R_RecursiveInteractionNode(light, lightSource, lightOrigin, node->children[0], clipFlags);
		R_RecursiveInteractionNode(light, lightSource, lightOrigin, node->children[1], clipFlags);
		return;
	}

	// If a leaf node, draw stuff
	leaf = (leaf_t *)node;

	if (!leaf->numMarkSurfaces)
		return;

	if (!lightSource->material->fogLight && !lightSource->material->blendLight && !lightSource->material->ambientLight)
	{
		// Check for door connected areas
		if (!r_skipAreas->integerValue && rg.renderView.areaBits)
		{
			if (!(rg.renderView.areaBits[leaf->area >> 3] & BIT(leaf->area & 7)))
				return;
		}
#if 0
		// Check if in light PVS
		if (!(lightSource->vis[leaf->cluster >> 3] & BIT(leaf->cluster & 7)))
			return;
#endif
	}

	// If it has a single surface, no need to cull again
	if (leaf->numMarkSurfaces == 1)
		clipFlags = CULL_IN;

	// Add all the surfaces
	for (i = 0, mark = leaf->firstMarkSurface; i < leaf->numMarkSurfaces; i++, mark++)
	{
		surface = *mark;

		if (surface->viewCount != rg.viewCount)
		{
			if (light->lightInFrustum || !light->castShadows)
				continue;   // Not visible, but may still cast visible shadows
		}

		if (surface->lightCount == rg.lightCount)
			continue;   // Already added this surface from another leaf
		surface->lightCount = rg.lightCount;

		if (lightSource->material->fogLight || lightSource->material->blendLight)
		{
			if (surface->texInfo->material->noFog)
				continue;   // No interactions
		}
		else
		{
			if (surface->texInfo->material->numStages == surface->texInfo->material->numAmbientStages)
				continue;   // No interactions
		}

		if (surface->texInfo->material->noShadows)
			continue;   // Don't bother drawing

		if (surface->texInfo->material->spectrum > lightSource->material->spectrum)
			continue;   // Not illuminated by this light

		// Cull face
		if (!lightSource->material->fogLight && !lightSource->material->blendLight && !lightSource->material->ambientLight)
		{
			if (!R_CullSurface(surface, rg.worldEntity, lightOrigin, clipFlags))
				continue;
#if 0
			if (surface->texInfo->material->cullType != CT_TWO_SIDED){
				side = PointOnPlaneSide(lightOrigin, 0.0, surface->plane);

				if (surface->texInfo->material->cullType == CT_BACK_SIDED){
					if (!(surface->flags & SURF_PLANEBACK)){
						if (side != PLANESIDE_BACK)
							continue;
					}
					else {
						if (side != PLANESIDE_FRONT)
							continue;
					}
				}
				else {
					if (!(surface->flags & SURF_PLANEBACK)){
						if (side != PLANESIDE_FRONT)
							continue;
					}
					else {
						if (side != PLANESIDE_BACK)
							continue;
					}
				}
			}
#endif
		}
#if 0
		if (!R_LightIntersectsBounds(lightSource, surface->mins, surface->maxs))
			continue;	// Not in light volume
#endif
		// Select rendering method
		caps = R_BoxInNearClipVolume(surface->mins, surface->maxs);

		// Add the surface
		if (light->castShadows)
			R_AddShadowMeshToLight(light, MESH_SURFACE, surface, surface->texInfo->material, rg.worldEntity, caps);

		R_AddInteractionMeshToLight(light, MESH_SURFACE, surface, surface->texInfo->material, rg.worldEntity);
	}
}

/*
 ==================
 R_AddWorldInteractions
 ==================
*/
static void R_AddWorldInteractions (light_t *light, lightSource_t *lightSource, const vec3_t lightOrigin)
{
	if (!rg.renderViewParms.primaryView)
		return;

	// TODO!!!
	if (lightSource->precached)
	{
		return;
	}

	// Recurse down the BSP tree
	R_RecursiveInteractionNode(light, lightSource, lightOrigin, rg.worldModel->nodes, LIGHT_PLANEBITS);
}

static void R_AddInlineModelInteractions (light_t *light, lightSource_t *lightSource, const vec3_t lightOrigin, renderEntity_t *entity)
{
}

/*
 ==================
 R_AddMD2ModelInteractions

 TODO: Cull surface?
 FIXME: Culling a box messes up
 FIXME2: viewCount seems to be always 0
 ==================
*/
static void R_AddMD2ModelInteractions (light_t *light, lightSource_t *lightSource, const vec3_t lightOrigin, renderEntity_t *entity)
{
	mdl_t		   *model = entity->model->alias;
	mdlFrame_t	   *curFrame, *oldFrame;
	mdlSurface_t   *surface;
	material_t	   *material;
	vec3_t		   mins, maxs;
	bool		   caps;
	float		   radius;
	int			   i, cullBits;

	if (entity->viewCount != rg.viewCount)
	{
		if (light->lightInFrustum || !light->castShadows)
			return;   // Not visible, but may still cast visible shadows
	}

	if (entity->renderFX & RF_VIEWERMODEL)
	{
		if (!r_skipSuppress->integerValue && rg.renderViewParms.subview == SUBVIEW_NONE)
			return;
	}
	if (entity->renderFX & RF_WEAPONMODEL)
	{
		if (!r_skipSuppress->integerValue && rg.renderViewParms.subview != SUBVIEW_NONE)
			return;
	}

	// Check if this entity emits this light
	if (VectorCompare(lightOrigin, entity->origin))
		return;

	// Find model radius
	if ((entity->frame < 0 || entity->frame >= model->numFrames) || (entity->oldFrame < 0 || entity->oldFrame >= model->numFrames))
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_AddAliasModelInteractions: no such frame %i to %i (%s)\n", entity->frame, entity->oldFrame, entity->model->realName);

		entity->frame = 0;
		entity->oldFrame = 0;
	}

	curFrame = model->frames + entity->frame;
	oldFrame = model->frames + entity->oldFrame;

	if (curFrame == oldFrame)
	{
		VectorCopy(curFrame->mins, mins);
		VectorCopy(curFrame->maxs, maxs);

		radius = curFrame->radius;
	}
	else
	{
		VectorMin(curFrame->mins, oldFrame->mins, mins);
		VectorMax(curFrame->maxs, oldFrame->maxs, maxs);

		if (curFrame->radius > oldFrame->radius)
			radius = curFrame->radius;
		else
			radius = oldFrame->radius;
	}

	// Cull
	if (entity->depthHack)
		cullBits = CULL_IN;
	else
	{
		cullBits = R_LightCullEntitySphere(lightSource, entity, radius, LIGHT_PLANEBITS);

		if (cullBits == CULL_OUT)
			return;

		if (cullBits != CULL_IN)
		{
			cullBits = R_LightCullEntityBox(lightSource, entity, mins, maxs, cullBits);

			if (cullBits == CULL_OUT)
				return;
		}
	}
#if 0
	// Cull
	if (R_CullSphere(entity->origin, radius, 31))
		return;

	if (!r_skipInteractionCulling->integerValue){
		if (!R_LightIntersectsSphere(lightSource, entity->origin, radius))
			return;		// Not in light volume
	}
#endif

	// Select rendering method
	if (entity->renderFX & RF_VIEWERMODEL)
		caps = true;
	else
		caps = R_SphereInNearClipVolume(entity->origin, radius);

	// If it has a single surface, no need to cull again
	if (model->numSurfaces == 1)
		cullBits = CULL_IN;

	// Add all the surfaces
	for (i = 0, surface = model->surfaces; i < model->numSurfaces; i++, surface++)
	{
		// Select material
		if (entity->customMaterial)
			material = entity->customMaterial;
		else
		{
			if (surface->numMaterials)
			{
				if (entity->skinIndex < 0 || entity->skinIndex >= surface->numMaterials)
				{
					Com_DevPrintf(S_COLOR_YELLOW "R_AddAliasModelInteractions: no such material %i (%s)\n", entity->skinIndex, entity->model->realName);

					entity->skinIndex = 0;
				}

				material = surface->materials[entity->skinIndex].material;
			}
			else
			{
				Com_DevPrintf(S_COLOR_YELLOW "R_AddAliasModelInteractions: no materials for surface (%s)\n", entity->model->realName);

				material = rg.defaultMaterial;
			}
		}

		if (!material->numStages)
			continue;

		if (lightSource->material->fogLight || lightSource->material->blendLight)
		{
			if (material->noFog)
				continue;   // No interactions
		}
		else
		{
			if (material->numStages == material->numAmbientStages)
				continue;	// No interactions
		}

		if (material->noShadows)
			continue;   // Don't bother drawing

		if (material->spectrum > lightSource->material->spectrum)
			continue;   // Not illuminated by this light

		// Add the surface
		if (light->castShadows)
			R_AddShadowMeshToLight(light, MESH_ALIASMODEL, surface, material, entity, caps);

		R_AddInteractionMeshToLight(light, MESH_ALIASMODEL, surface, material, entity);
	}
}

/*
 ==================
 R_AddEntityInteractions
 ==================
*/
static void R_AddEntityInteractions (light_t *light, lightSource_t *lightSource, const vec3_t lightOrigin)
{
	renderEntity_t   *entity;
	model_t			 *model;
	int				 i;

	for (i = 0, entity = rg.renderViewParms.renderEntities; i < rg.renderViewParms.numRenderEntities; i++, entity++)
	{
		if (entity->reType != RE_MODEL)
			continue;

		model = entity->model;

		if (!model || model->type == MODEL_BAD)
			continue;

		// Development tool
		if (r_singleEntity->integerValue != -1)
		{
			if (r_singleEntity->integerValue != entity->index)
				continue;
		}

		switch (model->type){
		case MODEL_INLINE:
			R_AddInlineModelInteractions(light, lightSource, lightOrigin, entity);
			break;
		case MODEL_MD2:
			R_AddMD2ModelInteractions(light, lightSource, lightOrigin, entity);
			break;
		}
	}
}

/*
 ==================
 R_CreateInteractionLists
 ==================
*/
static void R_CreateInteractionLists (light_t *light, lightSource_t *lightSource)
{
	cplane_t   *plane;
	vec3_t	   lightOrigin;
	int		   i;

	// Bump light count
	rg.lightCount++;

	// Clear lists
	light->numShadows[0] = light->numShadows[1] = 0;
	light->numInteractions[0] = light->numInteractions[1] = 0;

	// Determine if the light origin is inside the view frustum
	for (i = 0, plane = rg.renderViewParms.frustum; i < NUM_FRUSTUM_PLANES; i++, plane++)
	{
		if (!(rg.renderViewParms.planeBits & BIT(i)))
			continue;

		if (PointOnPlaneSide(light->origin, 0.0f, plane) == PLANESIDE_BACK)
			break;
	}

	if (i == 5)
		light->lightInFrustum = true;
	else
		light->lightInFrustum = false;

	// Determine if it casts shadows
	if (lightSource->noShadows || lightSource->material->noShadows)
		light->castShadows = false;
	else
		light->castShadows = true;

	// Combine origin and center into light origin
	if (lightSource->parallel)
		VectorMA(lightSource->origin, 999999, lightSource->center, lightOrigin);
	else
		VectorAdd(lightSource->origin, lightSource->center, lightOrigin);

	// Compute near clip volume for shadows
	R_SetupNearClipVolume(lightOrigin);

	if (lightSource->parallel || rg.renderViewParms.subview == SUBVIEW_MIRROR)
		r_nearClipVolume.FIXME = true;
	else
		r_nearClipVolume.FIXME = false;

	// Create them
	R_AddWorldInteractions(light, lightSource, lightOrigin);
	R_AddEntityInteractions(light, lightSource, lightOrigin);

	// Allocate lists
	if (light->numShadows[0])
	{
		light->castShadows = true;

		light->shadows[0] = (mesh_t *)Mem_Alloc(light->numShadows[0] * sizeof(mesh_t), rg.lightMemoryPool);
		memcpy(light->shadows[0], r_shadowsList[0], light->numShadows[0] * sizeof(mesh_t));
	}

	if (light->numShadows[1])
	{
		light->castShadows = true;

		light->shadows[1] = (mesh_t *)Mem_Alloc(light->numShadows[1] * sizeof(mesh_t), rg.lightMemoryPool);
		memcpy(light->shadows[1], r_shadowsList[1], light->numShadows[1] * sizeof(mesh_t));
	}

	if (light->numInteractions[0])
	{
		light->interactions[0] = (mesh_t *)Mem_Alloc(light->numInteractions[0] * sizeof(mesh_t), rg.lightMemoryPool);
		memcpy(light->interactions[0], r_interactionsList[0], light->numInteractions[0] * sizeof(mesh_t));
	}

	if (light->numInteractions[1])
	{
		light->interactions[1] = (mesh_t *)Mem_Alloc(light->numInteractions[1] * sizeof(mesh_t), rg.lightMemoryPool);
		memcpy(light->interactions[1], r_interactionsList[1], light->numInteractions[1] * sizeof(mesh_t));
	}
}


/*
 ==============================================================================

    RENDER LIGHTS

 ==============================================================================
*/


/*
 ==================
 R_AddLightToList
 ==================
*/
static void R_AddLightToList (lightSource_t *lightSource)
{
	viewData_t   *viewData = &rg.viewData;
	light_t		 *light;
	vec3_t		 axis[3], origin;
	vec3_t		 edge[2];

	if (lightSource->material->fogLight || lightSource->material->blendLight)
	{
		if (viewData->numFogLights == MAX_FOG_LIGHTS)
			Com_Error(ERR_DROP, "R_AddLightToList: MAX_FOG_LIGHTS hit");

		light = &viewData->fogLights[viewData->numFogLights++];
	}
	else
	{
		if (viewData->numLights == MAX_LIGHTS)
			Com_Error(ERR_DROP, "R_AddLightToList: MAX_LIGHTS hit");

		light = &viewData->lights[viewData->numLights++];
	}

	if (lightSource->parallel)
		VectorMA(lightSource->origin, 999999, lightSource->center, light->origin);
	else
		VectorAdd(lightSource->origin, lightSource->center, light->origin);

	light->material = lightSource->material;

	light->materialParms[0] = lightSource->materialParms[0] * rg.lightStyles[lightSource->style].rgb[0];
	light->materialParms[1] = lightSource->materialParms[1] * rg.lightStyles[lightSource->style].rgb[1];
	light->materialParms[2] = lightSource->materialParms[2] * rg.lightStyles[lightSource->style].rgb[2];
	light->materialParms[3] = lightSource->materialParms[3];
	light->materialParms[4] = lightSource->materialParms[4];
	light->materialParms[5] = lightSource->materialParms[5];
	light->materialParms[6] = lightSource->materialParms[6];
	light->materialParms[7] = lightSource->materialParms[7];

	VectorCopy(lightSource->corners[0], light->corners[0]);
	VectorCopy(lightSource->corners[1], light->corners[1]);
	VectorCopy(lightSource->corners[2], light->corners[2]);
	VectorCopy(lightSource->corners[3], light->corners[3]);
	VectorCopy(lightSource->corners[4], light->corners[4]);
	VectorCopy(lightSource->corners[5], light->corners[5]);
	VectorCopy(lightSource->corners[6], light->corners[6]);
	VectorCopy(lightSource->corners[7], light->corners[7]);

	// Compute visible fog plane or transformation matrix
	if (lightSource->material->fogLight)
	{
		VectorSubtract(lightSource->corners[3], lightSource->corners[2], edge[0]);
		VectorSubtract(lightSource->corners[1], lightSource->corners[2], edge[1]);
		CrossProduct(edge[1], edge[0], light->planeNormal);
		VectorNormalize(light->planeNormal);

		light->planeDist = DotProduct(lightSource->corners[0], light->planeNormal);
	}
	else
	{
		VectorScale(lightSource->axis[0], 0.5f / lightSource->radius[0], axis[0]);
		VectorScale(lightSource->axis[1], 0.5f / lightSource->radius[1], axis[1]);
		VectorScale(lightSource->axis[2], 0.5f / lightSource->radius[2], axis[2]);

		VectorNegate(lightSource->origin, origin);

		light->matrix[ 0] = axis[0][0];
		light->matrix[ 1] = axis[0][1];
		light->matrix[ 2] = axis[0][2];
		light->matrix[ 3] = 0.0f;
		light->matrix[ 4] = axis[1][0];
		light->matrix[ 5] = axis[1][1];
		light->matrix[ 6] = axis[1][2];
		light->matrix[ 7] = 0.0f;
		light->matrix[ 8] = axis[2][0];
		light->matrix[ 9] = axis[2][1];
		light->matrix[10] = axis[2][2];
		light->matrix[11] = 0.0f;
		light->matrix[12] = (axis[0][0] * origin[0] + axis[1][0] * origin[1] + axis[2][0] * origin[2]) + 0.5f;
		light->matrix[13] = (axis[0][1] * origin[0] + axis[1][1] * origin[1] + axis[2][1] * origin[2]) + 0.5f;
		light->matrix[14] = (axis[0][2] * origin[0] + axis[1][2] * origin[1] + axis[2][2] * origin[2]) + 0.5f;
		light->matrix[15] = 1.0f;
	}

	// Compute scissor rect
	R_SetScissorRect(light, lightSource);

	// Compute depth bounds
	R_SetDepthBounds(light, lightSource);

	// Create interaction lists
	R_CreateInteractionLists(light, lightSource);
}

/*
 ==================
 R_AddLights
 ==================
*/
void R_AddLights ()
{
	lightSource_t	*lightSource;
	int				cullBits;
	int				i;

	if (r_skipLights->integerValue)
		return;

	for (i = 0, lightSource = r_staticLights; i < r_numStaticLights; i++, lightSource++)
	{
		// Development tool
		if (r_singleLight->integerValue != -1)
		{
			if (r_singleLight->integerValue != lightSource->index)
				continue;
		}

		// Frustum cull
		cullBits = R_CullLightBox(lightSource->mins, lightSource->maxs, rg.renderViewParms.planeBits);

		if (cullBits == CULL_OUT)
			continue;

		if (cullBits != CULL_IN)
		{
			if (R_CullLightVolumeBox(lightSource->corners, cullBits) == CULL_OUT)
				continue;
		}

		// Add the light
		R_AddLightToList(lightSource);
	}
}

/*
 ==================
 R_ClearLightMeshLists
 ==================
*/
void R_ClearLightMeshLists ()
{
	light_t *light;
	int		i;

	// Free shadow and light interaction lists
	for (i = 0, light = rg.viewData.lights; i < rg.viewData.numLights; i++, light++)
	{
		if (light->shadows[0])
		{
			Mem_Free(light->shadows[0]);
			light->shadows[0] = NULL;
		}

		if (light->shadows[1])
		{
			Mem_Free(light->shadows[1]);
			light->shadows[1] = NULL;
		}

		if (light->interactions[0])
		{
			Mem_Free(light->interactions[0]);
			light->interactions[0] = NULL;
		}

		if (light->interactions[1])
		{
			Mem_Free(light->interactions[1]);
			light->interactions[1] = NULL;
		}
	}

	// Clear light lists
	rg.viewData.numLights = rg.viewData.firstLight = 0;
	rg.viewData.numFogLights = rg.viewData.firstFogLight = 0;
}


/*
 ==============================================================================

    INTEGRATED LIGHT EDITOR

 ==============================================================================
*/

struct lightEditor_t {
	bool					active;

	lightSource_t			*lights[MAX_STATIC_LIGHTS];
	int						numLights;

	lightSource_t			*focusLight;
	lightSource_t			*editLight;
};

static lightEditor_t		r_lightEditor;


/*
 ==================
 R_UpdateLight

 Called by the light properties editor to update the current light
 ==================
*/
static void R_UpdateLight (lightProperties_t *lightProperties)
{
	lightSource_t   *lightSource = r_lightEditor.editLight;
	vec3_t			tmp;
	float			dot;
	int				i;

	if (!r_lightEditor.active)
		return;

	if (!r_lightEditor.editLight)
		return;

	// Set the properties
	VectorCopy(lightProperties->origin, lightSource->origin);
	VectorCopy(lightProperties->center, lightSource->center);
	VectorCopy(lightProperties->angles, lightSource->angles);
	VectorCopy(lightProperties->radius, lightSource->radius);
	lightSource->style = lightProperties->style;
	lightSource->detailLevel = lightProperties->detailLevel;
	lightSource->parallel = lightProperties->parallel;
	lightSource->noShadows = lightProperties->noShadows;
	lightSource->material = R_FindMaterial(lightProperties->material, MT_LIGHT, 0);
	lightSource->materialParms[0] = lightProperties->materialParms[0];
	lightSource->materialParms[1] = lightProperties->materialParms[1];
	lightSource->materialParms[2] = lightProperties->materialParms[2];
	lightSource->materialParms[3] = lightProperties->materialParms[3];
	lightSource->materialParms[4] = lightProperties->materialParms[4];
	lightSource->materialParms[5] = lightProperties->materialParms[5];
	lightSource->materialParms[6] = lightProperties->materialParms[6];
	lightSource->materialParms[7] = lightProperties->materialParms[7];

	// Compute axes
	AnglesToAxis(lightProperties->angles, lightSource->axis);

	// Compute the corners of the bounding volume
	for (i = 0; i < 8; i++)
	{
		tmp[0] = (i & 1) ? -lightSource->radius[0] : lightSource->radius[0];
		tmp[1] = (i & 2) ? -lightSource->radius[1] : lightSource->radius[1];
		tmp[2] = (i & 4) ? -lightSource->radius[2] : lightSource->radius[2];

		// Rotate and translate
		VectorRotate(tmp, lightSource->axis, lightSource->corners[i]);
		VectorAdd(lightSource->corners[i], lightSource->origin, lightSource->corners[i]);
	}

	// Check if it's rotated
	lightSource->rotated = !VectorCompare(lightProperties->angles, vec3_origin);

	// Compute mins/maxs
	VectorSubtract(lightSource->origin, lightSource->radius, lightSource->mins);
	VectorAdd(lightSource->origin, lightSource->radius, lightSource->maxs);

	// Compute frustum planes
	for (i = 0; i < 3; i++)
	{
		dot = DotProduct(lightSource->origin, lightSource->axis[i]);

		VectorCopy(lightSource->axis[i], lightSource->frustum[i*2+0].normal);
		lightSource->frustum[i*2+0].dist = dot - lightSource->radius[i];
		lightSource->frustum[i*2+0].type = PlaneTypeForNormal(lightSource->frustum[i*2+0].normal);
		SetPlaneSignbits(&lightSource->frustum[i*2+0]);

		VectorNegate(lightSource->axis[i], lightSource->frustum[i*2+1].normal);
		lightSource->frustum[i*2+1].dist = -dot - lightSource->radius[i];
		lightSource->frustum[i*2+1].type = PlaneTypeForNormal(lightSource->frustum[i*2+1].normal);
		SetPlaneSignbits(&lightSource->frustum[i*2+1]);
	}

	// Find the leaf
	VectorAdd(lightSource->origin, lightSource->center, tmp);

	lightSource->leaf = R_PointInLeaf(tmp);
}

/*
 ==================
 R_RemoveLight

 Called by the light properties editor to remove the current light
 ==================
*/
static void R_RemoveLight ()
{
	lightSource_t   *lightSource;
	int				i, j;

	if (!r_lightEditor.active)
		return;

	if (!r_lightEditor.editLight)
		return;

	for (i = 0, lightSource = r_staticLights; i < r_numStaticLights; i++, lightSource++)
	{
		if (lightSource != r_lightEditor.editLight)
			continue;

		for (j = i; j < r_numStaticLights - 1; j++)
			memcpy(&r_staticLights[j], &r_staticLights[j+1], sizeof(lightSource_t));

		r_numStaticLights--;
		break;
	}

	for (i = 0, lightSource = r_staticLights; i < r_numStaticLights; i++, lightSource++)
		lightSource->index = i;

	// No light is being edited now
	r_lightEditor.editLight = NULL;
}

/*
 ==================
 R_CloseLight

 Called by the light properties editor when closed
 ==================
*/
static void R_CloseLight ()
{
	if (!r_lightEditor.active)
		return;

	if (!r_lightEditor.editLight)
		return;

	r_lightEditor.active = false;

	// No light is being edited now
	r_lightEditor.focusLight = NULL;
	r_lightEditor.editLight = NULL;

	// Close the editor
	ED_CloseEditor();
}

/*
 ==================
 R_EditLight

 Called by the client system for mouse left-button down events.
 Returns true if a light has been selected for editing, in which case
 the client should skip game commands for said event.
 ==================
*/
bool R_EditLight ()
{
	lightProperties_t	lightProperties;

	if (!r_lightEditor.active)
		return false;   // Not active

	if (!r_lightEditor.focusLight)
		return false;	// No light has focus

	// If already editing a light, apply the changes
	if (r_lightEditor.editLight)
		Win_ApplyLightProperties();

	// Edit the light that has focus
	r_lightEditor.editLight = r_lightEditor.focusLight;

	VectorCopy(r_lightEditor.editLight->origin, lightProperties.origin);
	VectorCopy(r_lightEditor.editLight->center, lightProperties.center);
	VectorCopy(r_lightEditor.editLight->angles, lightProperties.angles);
	VectorCopy(r_lightEditor.editLight->radius, lightProperties.radius);
	lightProperties.style = r_lightEditor.editLight->style;
	lightProperties.detailLevel = r_lightEditor.editLight->detailLevel;
	lightProperties.parallel = r_lightEditor.editLight->parallel;
	lightProperties.noShadows = r_lightEditor.editLight->noShadows;
	Str_Copy(lightProperties.material, r_lightEditor.editLight->material->name, sizeof(lightProperties.material));
	lightProperties.materialParms[0] = r_lightEditor.editLight->materialParms[0];
	lightProperties.materialParms[1] = r_lightEditor.editLight->materialParms[1];
	lightProperties.materialParms[2] = r_lightEditor.editLight->materialParms[2];
	lightProperties.materialParms[3] = r_lightEditor.editLight->materialParms[3];
	lightProperties.materialParms[4] = r_lightEditor.editLight->materialParms[4];
	lightProperties.materialParms[5] = r_lightEditor.editLight->materialParms[5];
	lightProperties.materialParms[6] = r_lightEditor.editLight->materialParms[6];
	lightProperties.materialParms[7] = r_lightEditor.editLight->materialParms[7];

	Win_EditLightProperties(&lightProperties);

	Com_Printf("Editing light #%i\n", r_lightEditor.editLight->index);

	return true;
}

/*
 ==================
 R_AddEditorLights
 ==================
*/
void R_AddEditorLights ()
{
	lightSource_t   *lightSource;
	trace_t			trace;
	cplane_t		*plane;
	vec3_t			mins = {-5, -5, -5}, maxs = {5, 5, 5};
	vec3_t			start, end;
	int				headNode;
	int				i, j;

	if (!r_lightEditor.active)
		return;   // Not active

	if (!rg.renderViewParms.primaryView)
		return;   // Ignore non-primary views

	if (rg.renderViewParms.subview != SUBVIEW_NONE)
		return;   // Ignore subviews

	r_lightEditor.numLights = 0;

	// Find visible lights
	for (i = 0, lightSource = r_staticLights; i < r_numStaticLights; i++, lightSource++)
	{
		// Always add the light that is being edited
		if (lightSource == r_lightEditor.editLight)
		{
			r_lightEditor.lights[r_lightEditor.numLights++] = lightSource;
			continue;
		}

		// Frustum cull
		for (j = 0, plane = rg.renderViewParms.frustum; j < NUM_FRUSTUM_PLANES; j++, plane++)
		{
			if (SphereOnPlaneSide(lightSource->origin, 10, plane) == PLANESIDE_BACK)
				break;
		}

		if (j != NUM_FRUSTUM_PLANES)
			continue;

		// Distance cull
		if (Distance(lightSource->origin, rg.renderView.viewOrigin) > 512.0f)
			continue;

		// Add it to the list
		r_lightEditor.lights[r_lightEditor.numLights++] = lightSource;
	}

	// Find the light that has focus, if any
	r_lightEditor.focusLight = NULL;

	VectorCopy(rg.renderView.viewOrigin, start);
	VectorMA(rg.renderView.viewOrigin, 8192, rg.renderView.viewAxis[0], end);

	headNode = CM_HeadNodeForBox(mins, maxs);

	for (i = 0; i < r_lightEditor.numLights; i++)
	{
		lightSource = r_lightEditor.lights[i];

		trace = CM_TransformedBoxTrace(start, end, vec3_origin, vec3_origin, headNode, MASK_ALL, lightSource->origin, lightSource->angles);
		if (trace.fraction < 1.0f)
		{
			// This light has focus
			r_lightEditor.focusLight = lightSource;
		}
	}
}

/*
 ==================
 R_DrawEditorLights
 ==================
*/
void R_DrawEditorLights ()
{
	lightSource_t   *lightSource;
	vec3_t			corners[8], corner;
	vec3_t			color;
	int				i, j;

	if (!r_lightEditor.active)
		return;   // Not active

	if (!r_lightEditor.numLights && !r_lightEditor.focusLight && !r_lightEditor.editLight)
		return;   // No lights

	// Draw all lights
	for (i = 0; i < r_lightEditor.numLights; i++)
	{
		lightSource = r_lightEditor.lights[i];

		// Create a small box
		for (j = 0; j < 8; j++)
		{
			corner[0] = (j & 1) ? -5 : 5;
			corner[1] = (j & 2) ? -5 : 5;
			corner[2] = (j & 4) ? -5 : 5;

			// Rotate and translate into world space
			VectorRotate(corner, lightSource->axis, corners[j]);
			VectorAdd(corners[j], lightSource->origin, corners[j]);
		}

		// Draw it
		if (lightSource == r_lightEditor.focusLight || lightSource == r_lightEditor.editLight)
		{
			MakeRGBA(color, 1.0f, 0.0f, 0.0f, 1.0f);
			ED_DrawBox(corners, color);
		}
		else
		{
			MakeRGBA(color, 1.0f, 1.0f, 1.0f, 1.0f);
			ED_DrawBox(corners, color);
		}

		// If editing this light, draw the light center
		if (lightSource == r_lightEditor.editLight)
		{
			// Create a small box
			for (j = 0; j < 8; j++)
			{
				corner[0] = (j & 1) ? -2 : 2;
				corner[1] = (j & 2) ? -2 : 2;
				corner[2] = (j & 4) ? -2 : 2;

				// Rotate and translate into world space
				VectorRotate(corner, lightSource->axis, corners[j]);
				VectorAdd(corners[j], lightSource->origin, corners[j]);
				VectorAdd(corners[j], lightSource->center, corners[j]);
			}

			// Draw it
			MakeRGBA(color, 1.0f, 0.0f, 0.0f, 1.0f);
			ED_DrawBox(corners, color);
		}

		// TODO: Draw editor material name

		// Check for errors
		if (!r_ignoreGLErrors->integerValue)
			GL_CheckForErrors();
	}
}


/*
 ==============================================================================

    CONSOLE COMMANDS

 ==============================================================================
*/


/*
 ==================
 R_EditLights_f
 ==================
*/
static void R_EditLights_f ()
{
	editorCallbacks_t	callbacks;

	// Launch the light editor
	callbacks.createWindow = Win_CreateLightEditorWindow;
	callbacks.destroyWindow = Win_ShutdownLightEditor;
	callbacks.mouseEvent = R_EditLight;
	callbacks.draw = R_DrawEditorLights;

	if (!ED_LaunchEditor("light", &callbacks))
		return;

	r_lightEditor.active = true;

	r_lightEditor.focusLight = NULL;
	r_lightEditor.editLight = NULL;

	// Set event callbacks
	Win_SetLightPropertiesCallbacks(R_UpdateLight, R_RemoveLight, R_CloseLight);

	// Enum material scripts
	R_EnumMaterialScripts(Win_AddLightPropertiesMaterial);
}

/*
 ==================
 R_ClearLights_f
 ==================
*/
static void R_ClearLights_f ()
{
	if (!r_lightEditor.active)
	{
		Com_Printf("The light editor is not active\n");
		return;
	}

	if (r_lightEditor.editLight)
	{
		Com_Printf("Still editing a light\n");
		return;
	}

	// Remove all the lights
	Com_Printf("Cleared %i lights\n", r_numStaticLights);

	r_numStaticLights = 0;
}

/*
 ==================
 R_SaveLights_f
 ==================
*/
static void R_SaveLights_f ()
{
	fileHandle_t	f;
	lightSource_t   *lightSource;
	char			name[MAX_PATH_LENGTH];
	int				i;

	if (!r_lightEditor.active)
	{
		Com_Printf("The light editor is not active\n");
		return;
	}

	if (r_lightEditor.editLight)
	{
		Com_Printf("Still editing a light\n");
		return;
	}

	if (!r_numStaticLights)
	{
		Com_Printf("No lights!\n");
		return;
	}

	// Write the script file
	Str_SPrintf(name, sizeof(name), "%s.txt", rg.worldModel->name);

	FS_OpenFile(name, FS_WRITE, &f);
	if (!f)
	{
		Com_Printf("Couldn't write %s\n", name);
		return;
	}

	for (i = 0, lightSource = r_staticLights; i < r_numStaticLights; i++, lightSource++)
	{
		FS_Printf(f, "{\r\n");

		FS_Printf(f, "origin %g %g %g\r\n", lightSource->origin[0], lightSource->origin[1], lightSource->origin[2]);

		if (lightSource->center[0] != 0.0f || lightSource->center[1] != 0.0f || lightSource->center[2] != 0.0f)
			FS_Printf(f, "center %g %g %g\r\n", lightSource->center[0], lightSource->center[1], lightSource->center[2]);

		if (lightSource->angles[0] != 0.0f || lightSource->angles[1] != 0.0f || lightSource->angles[2] != 0.0f)
			FS_Printf(f, "angles %g %g %g\r\n", lightSource->angles[0], lightSource->angles[1], lightSource->angles[2]);

		if (lightSource->radius[0] != 128.0f || lightSource->radius[1] != 128.0f || lightSource->radius[2] != 128.0f)
			FS_Printf(f, "radius %g %g %g\r\n", lightSource->radius[0], lightSource->radius[1], lightSource->radius[2]);

		if (lightSource->style != 0)
			FS_Printf(f, "style %i\r\n", lightSource->style);

		if (lightSource->detailLevel != 10)
			FS_Printf(f, "detailLevel %i\r\n", lightSource->detailLevel);

		if (lightSource->parallel)
			FS_Printf(f, "parallel\r\n");

		if (lightSource->noShadows)
			FS_Printf(f, "noShadows\r\n");

		if (lightSource->material != rg.defaultLightMaterial)
			FS_Printf(f, "material %s\r\n", lightSource->material->name);

		if (lightSource->materialParms[0] != 1.0f || lightSource->materialParms[1] != 1.0f || lightSource->materialParms[2] != 1.0f)
			FS_Printf(f, "color %g %g %g\r\n", lightSource->materialParms[0], lightSource->materialParms[1], lightSource->materialParms[2]);

		if (lightSource->materialParms[3] != 1.0f)
			FS_Printf(f, "materialParm3 %g\r\n", lightSource->materialParms[3]);

		if (lightSource->materialParms[4] != 0.0f)
			FS_Printf(f, "materialParm4 %g\r\n", lightSource->materialParms[4]);

		if (lightSource->materialParms[5] != 0.0f)
			FS_Printf(f, "materialParm5 %g\r\n", lightSource->materialParms[5]);

		if (lightSource->materialParms[6] != 0.0f)
			FS_Printf(f, "materialParm6 %g\r\n", lightSource->materialParms[6]);

		if (lightSource->materialParms[7] != 0.0f)
			FS_Printf(f, "materialParm7 %g\r\n", lightSource->materialParms[7]);

		FS_Printf(f, "}\r\n");
	}

	FS_CloseFile(f);

	Com_Printf("Wrote %s with %i lights\n", name, r_numStaticLights);
}

/*
 ==================
 R_AddLight_f
 ==================
*/
static void R_AddLight_f ()
{
	lightSource_t		*lightSource;
	lightProperties_t   lightProperties;
	vec3_t				corner;
	float				dot;
	int					i;

	if (!r_lightEditor.active)
	{
		Com_Printf("The light editor is not active\n");
		return;
	}

	// Add a new light in front of the camera
	if (r_numStaticLights == MAX_STATIC_LIGHTS)
	{
		Com_Printf("Too many lights!\n");
		return;
	}

	lightSource = &r_staticLights[r_numStaticLights++];

	// Set the properties
	VectorMA(rg.renderView.viewOrigin, 100, rg.renderView.viewAxis[0], lightSource->origin);

	lightSource->index = r_numStaticLights - 1;
	SnapVector(lightSource->origin);
	VectorClear(lightSource->center);
	VectorClear(lightSource->angles);
	VectorSet(lightSource->radius, 128, 128, 128);
	lightSource->style = 0;
	lightSource->detailLevel = 10;
	lightSource->parallel = false;
	lightSource->noShadows = false;
	lightSource->material = rg.defaultLightMaterial;
	lightSource->materialParms[MATERIALPARM_RED] = 1.0f;
	lightSource->materialParms[MATERIALPARM_GREEN] = 1.0f;
	lightSource->materialParms[MATERIALPARM_BLUE] = 1.0f;
	lightSource->materialParms[MATERIALPARM_ALPHA] = 1.0f;
	lightSource->materialParms[MATERIALPARM_TIME_OFFSET] = 0.0f;
	lightSource->materialParms[MATERIALPARM_DIVERSITY] = 0.0f;
	lightSource->materialParms[MATERIALPARM_GENERAL] = 0.0f;
	lightSource->materialParms[MATERIALPARM_MODE] = 0.0f;

	// Compute axes
	AxisClear(lightSource->axis);

	// Compute the corners of the bounding volume
	for (i = 0; i < 8; i++)
	{
		corner[0] = (i & 1) ? -lightSource->radius[0] : lightSource->radius[0];
		corner[1] = (i & 2) ? -lightSource->radius[1] : lightSource->radius[1];
		corner[2] = (i & 4) ? -lightSource->radius[2] : lightSource->radius[2];

		// Rotate and translate
		VectorRotate(corner, lightSource->axis, lightSource->corners[i]);
		VectorAdd(lightSource->corners[i], lightSource->origin, lightSource->corners[i]);
	}

	// Check if it's rotated
	lightSource->rotated = false;

	// Compute mins/maxs
	VectorSubtract(lightSource->origin, lightSource->radius, lightSource->mins);
	VectorAdd(lightSource->origin, lightSource->radius, lightSource->maxs);

	// Compute frustum planes
	for (i = 0; i < 3; i++)
	{
		dot = DotProduct(lightSource->origin, lightSource->axis[i]);

		VectorCopy(lightSource->axis[i], lightSource->frustum[i*2+0].normal);
		lightSource->frustum[i*2+0].dist = dot - lightSource->radius[i];
		lightSource->frustum[i*2+0].type = PlaneTypeForNormal(lightSource->frustum[i*2+0].normal);
		SetPlaneSignbits(&lightSource->frustum[i*2+0]);

		VectorNegate(lightSource->axis[i], lightSource->frustum[i*2+1].normal);
		lightSource->frustum[i*2+1].dist = -dot - lightSource->radius[i];
		lightSource->frustum[i*2+1].type = PlaneTypeForNormal(lightSource->frustum[i*2+1].normal);
		SetPlaneSignbits(&lightSource->frustum[i*2+1]);
	}

	// Find the leaf
	VectorAdd(lightSource->origin, lightSource->center, corner);

	lightSource->leaf = R_PointInLeaf(corner);

	// If still editing a light, apply the changes
	if (r_lightEditor.editLight)
		Win_ApplyLightProperties();

	// Edit the new light
	r_lightEditor.editLight = lightSource;

	VectorMA(rg.renderView.viewOrigin, 100, rg.renderView.viewAxis[0], lightProperties.origin);

	SnapVector(lightProperties.origin);
	VectorClear(lightProperties.center);
	VectorClear(lightProperties.angles);
	VectorSet(lightProperties.radius, 128, 128, 128);
	lightProperties.style = 0;
	lightProperties.detailLevel = 10;
	lightProperties.parallel = false;
	lightProperties.noShadows = false;
	Str_Copy(lightProperties.material, rg.defaultLightMaterial->name, sizeof(lightProperties.material));
	lightProperties.materialParms[0] = 1.0f;
	lightProperties.materialParms[1] = 1.0f;
	lightProperties.materialParms[2] = 1.0f;
	lightProperties.materialParms[3] = 1.0f;
	lightProperties.materialParms[4] = 0.0f;
	lightProperties.materialParms[5] = 0.0f;
	lightProperties.materialParms[6] = 0.0f;
	lightProperties.materialParms[7] = 0.0f;

	Win_EditLightProperties(&lightProperties);

	Com_Printf("Added light #%i at %i, %i, %i\n", lightSource->index, (int)lightSource->origin[0], (int)lightSource->origin[1], (int)lightSource->origin[2]);
}


/*
 ==============================================================================

    INITIALIZATION AND SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 R_InitLightEditor

 TODO: Add "copyLight"
 ==================
*/
void R_InitLightEditor ()
{
	// Add commands
	Cmd_AddCommand("editLights", R_EditLights_f);
	Cmd_AddCommand("clearLights", R_ClearLights_f);
	Cmd_AddCommand("saveLights", R_SaveLights_f);
	Cmd_AddCommand("addLight", R_AddLight_f);
}

/*
 ==================
 R_ShutdownLightEditor

 TODO: Free static lights and stuff too?
 ==================
*/
void R_ShutdownLightEditor ()
{
	// Remove commands
	Cmd_RemoveCommand("editLights");
	Cmd_RemoveCommand("clearLights");
	Cmd_RemoveCommand("saveLights");
	Cmd_RemoveCommand("addLight");
	
	// Close light editor if active
	if (r_lightEditor.active)
		ED_CloseEditor();

	// Clear light editor list
	memset(&r_lightEditor, 0, sizeof(lightEditor_t));
}